#if !defined(DATA_ADAPTER_H_)
#define DATA_ADAPTER_H_

#include <boost/json.hpp>
#include <mutex>
#include <string>
#include <thread>

#include "common/basic_type.h"
#include "common/circular_queue.h"
#include "common/file_helper.h"
#include "data_manager.h"
#include "preprocess/image_process.h"

namespace autodrive {
namespace perception {
class DataAdapter final {
 public:
  DataAdapter(DataAdapter &&) = delete;
  DataAdapter(const DataAdapter &) = delete;
  DataAdapter &operator=(DataAdapter &&) = delete;
  DataAdapter &operator=(const DataAdapter &) = delete;
  ~DataAdapter();

 public:
  ///@brief data buffer number
  static constexpr std::uint32_t kDataBufferNum{8U};

  using ImageSourceBuffer = CircularQueue<ImageSource<float>, kDataBufferNum>;
  using ImageSourceBufferPtr = std::shared_ptr<ImageSourceBuffer>;

  using PointCloudBuffer = CircularQueue<PointCloudSource, kDataBufferNum>;
  using PointCloudBufferPtr = std::shared_ptr<PointCloudBuffer>;

  /// @brief image process parameters
  struct ImageProcessParameters {
    /// @brief identity
    std::string id;
    /// @brief roi
    Rect crop_area;
    /// @brief
    Size resize_size;
    /// @brief image order
    std::uint8_t order;
    /// @brief norm mean
    std::vector<float> norm_mean;
    /// @brief norm standard
    std::vector<float> norm_std;
  };

  struct PointCloudProcessParameters {
    /// @brief lidar names
    std::vector<std::string> lidar_names;
  };

  struct DataAdapterSettings {
    /// @brief data manager settings
    DataManager::DataManagerSettings data_manager_settings;
    /// @brief image process parameters
    std::vector<ImageProcessParameters> image_process_parameters_set;
    /// @brief point cloud process parameters
    PointCloudProcessParameters point_cloud_process_parameters;
  };

 public:
  /// @brief get single instance
  /// @return instance
  static DataAdapter &GetInstance() noexcept;

  /// @brief get init status
  /// @return init flag
  inline bool IsInit() const noexcept { return is_init_; }

  /// @brief get run status
  /// @return run flag
  inline bool IsRun() const noexcept { return is_run_; }

  /// @brief get settings
  /// @return DataAdapterSettings
  inline DataAdapterSettings const &Settings() const noexcept {
    return settings_;
  }

  /// @brief get image source buffer pointer
  /// @return image source buffer pointer
  inline ImageSourceBufferPtr GetImageSourceBufferPtr() const noexcept {
    return image_source_buffer_ptr_;
  }

  /// @brief get point cloud buffer pointer
  /// @return point cloud buffer pointer
  inline PointCloudBufferPtr GetPointCloudBufferPtr() const noexcept {
    return point_cloud_buffer_ptr_;
  }

  /// @brief get calib data set pointer
  /// @return calib data set pointer
  inline CalibDataSetPtr GetCalibDataSetPtr() const noexcept {
    return calib_data_set_ptr_;
  }

  /// @brief do processor init
  /// @param err error message
  /// @return false on error
  void Init() noexcept(false);

  /// @brief
  void Start() noexcept;

  /// @brief
  void Stop() noexcept;

 protected:
  DataAdapter();

  /// @brief
  /// @return
  DataAdapterSettings gen_default() const noexcept;

  /// @brief
  /// @param dataAdapterPtr
  static void main_task(DataAdapter *dataAdapterPtr) noexcept;

  /// @brief sleep for millisecond
  /// @param milliseconds
  void msleep(std::uint32_t milliseconds) noexcept {
    std::chrono::milliseconds dura{milliseconds};
    std::this_thread::sleep_for(dura);
  }

  /// @brief update calib
  /// @param src_data_ptr source data pointer
  /// @param id calib id
  /// @param dest_data_ptr destination data pointer
  void update_calib(
      DataScene::CalibDataArrayPtr<DataScene::MatrixData> src_data_ptr,
      std::string const &id, CalibDataSetPtr dest_data_ptr) noexcept(false);

  /// @brief gen image source using camera data set and image process parameter
  /// @param camera_data_set camera data set
  /// @param para image process paras
  /// @param image_source output image source
  void gen_image_source(DataScene::CameraDataArray const &camera_data_set,
                        std::vector<ImageProcessParameters> const &paras,
                        ImageSource<float> &image_source) noexcept(false);

  /// @brief gen point cloud source
  /// @param lidar_data_set lidar data set
  /// @param paras PointCloudProcessParameters
  /// @param pcl_source output pointcloud source
  void gen_point_cloud_source(DataScene::LidarDataArray const &lidar_data_set,
                              PointCloudProcessParameters const &paras,
                              PointCloudSource &pcl_source) noexcept(false);

 private:
  /// @brief init flag
  bool is_init_;
  /// @brief run flag
  bool is_run_;
  /// @brief command to stop processor
  bool cmd_stop_;
  /// @brief lock
  std::mutex lock_;
  /// @brief  run call once flag
  mutable std::once_flag run_flag_;
  /// @brief  init call once flag
  mutable std::once_flag init_flag_;
  /// @brief bev fusion processor settings
  DataAdapterSettings settings_;
  /// @brief main task
  std::thread task_thread_;

  /// @brief calib data array pointer
  CalibDataSetPtr calib_data_set_ptr_;

  /// @brief image source buffer pointer
  ImageSourceBufferPtr image_source_buffer_ptr_;

  /// @brief point cloud buffer pointer
  PointCloudBufferPtr point_cloud_buffer_ptr_;
};
}  // namespace perception
}  // namespace autodrive

namespace boost::json {
using namespace autodrive::perception;

inline void tag_invoke(value_from_tag, value &jv,
                       DataManager::DataManagerSettings const &d) {
  jv = {{"data_scene_paths", value_from(d.data_scene_paths)},
        {"load_data_frame_settings", value_from(d.load_data_frame_settings)},
        {"cycle", value_from(d.cycle)}};
}

inline DataManager::DataManagerSettings tag_invoke(
    value_to_tag<DataManager::DataManagerSettings>,
    boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  DataManager::DataManagerSettings settings;
  settings.data_scene_paths =
      deserialize_obj<std::vector<std::string>>(obj, "data_scene_paths");
  settings.load_data_frame_settings =
      deserialize_obj<DataScene::LoadDataFrameSettings>(
          obj, "load_data_frame_settings");
  settings.cycle = deserialize_obj<std::int32_t>(obj, "cycle");
  return settings;
}

inline void tag_invoke(value_from_tag, value &jv,
                       DataScene::LoadDataFrameSettings const &l) {
  jv = {{"lidars", value_from(l.lidars)},
        {"cameras", value_from(l.cameras)},
        {"radars", value_from(l.radars)}};
}

inline DataScene::LoadDataFrameSettings tag_invoke(
    value_to_tag<DataScene::LoadDataFrameSettings>, value const &jv) {
  auto const &obj = jv.as_object();
  DataScene::LoadDataFrameSettings settings;
  settings.lidars = deserialize_obj<std::vector<std::string>>(obj, "lidars");
  settings.cameras = deserialize_obj<std::vector<std::string>>(obj, "cameras");
  settings.radars = deserialize_obj<std::vector<std::string>>(obj, "radars");
  return settings;
}

inline void tag_invoke(boost::json::value_from_tag, boost::json::value &jv,
                       autodrive::perception::Rect const &r) {
  jv = {{"x", r.x}, {"y", r.y}, {"height", r.height}, {"width", r.width}};
}

inline autodrive::perception::Rect tag_invoke(
    boost::json::value_to_tag<autodrive::perception::Rect>,
    boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  autodrive::perception::Rect rect;
  rect.x = deserialize_obj<std::int32_t>(obj, "x");
  rect.y = deserialize_obj<std::int32_t>(obj, "y");
  rect.height = deserialize_obj<std::int32_t>(obj, "height");
  rect.width = deserialize_obj<std::int32_t>(obj, "width");
  return rect;
}

inline void tag_invoke(boost::json::value_from_tag, boost::json::value &jv,
                       autodrive::perception::Size const &s) {
  jv = {{"height", s.height}, {"width", s.width}};
}

inline autodrive::perception::Size tag_invoke(
    boost::json::value_to_tag<autodrive::perception::Size>,
    boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  autodrive::perception::Size s;
  s.height = deserialize_obj<std::int32_t>(obj, "height");
  s.width = deserialize_obj<std::int32_t>(obj, "width");
  return s;
}

inline void tag_invoke(
    boost::json::value_from_tag, boost::json::value &jv,
    autodrive::perception::DataAdapter::ImageProcessParameters const &i) {
  jv = {{"id", i.id},
        {"crop_area", value_from(i.crop_area)},
        {"resize_size", value_from(i.resize_size)},
        {"order", i.order},
        {"norm_mean", value_from(i.norm_mean)},
        {"norm_std", value_from(i.norm_std)}};
}

inline autodrive::perception::DataAdapter::ImageProcessParameters tag_invoke(
    boost::json::value_to_tag<
        autodrive::perception::DataAdapter::ImageProcessParameters>,
    boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  autodrive::perception::DataAdapter::ImageProcessParameters p;
  p.id = deserialize_obj<std::string>(obj, "id");
  p.crop_area = deserialize_obj<autodrive::perception::Rect>(obj, "crop_area");
  p.resize_size =
      deserialize_obj<autodrive::perception::Size>(obj, "resize_size");
  p.order = deserialize_obj<std::uint8_t>(obj, "order");
  p.norm_mean = deserialize_obj<std::vector<float>>(obj, "norm_mean");
  p.norm_std = deserialize_obj<std::vector<float>>(obj, "norm_std");
  return p;
}

inline void tag_invoke(
    boost::json::value_from_tag, boost::json::value &jv,
    autodrive::perception::DataAdapter::PointCloudProcessParameters const &i) {
  jv = {{"lidar_names", i.lidar_names}};
}

inline autodrive::perception::DataAdapter::PointCloudProcessParameters
tag_invoke(boost::json::value_to_tag<
               autodrive::perception::DataAdapter::PointCloudProcessParameters>,
           boost::json::value const &jv) {
  auto const &obj = jv.as_object();
  autodrive::perception::DataAdapter::PointCloudProcessParameters p;
  p.lidar_names = deserialize_obj<std::vector<std::string>>(obj, "lidar_names");
  return p;
}

inline void tag_invoke(value_from_tag, value &jv,
                       DataAdapter::DataAdapterSettings const &d) {
  jv = {
      {"data_manager_settings", value_from(d.data_manager_settings)},
      {"image_process_parameters", value_from(d.image_process_parameters_set)},
      {"point_cloud_process_parameters",
       value_from(d.point_cloud_process_parameters)}};
}

inline DataAdapter::DataAdapterSettings tag_invoke(
    value_to_tag<DataAdapter::DataAdapterSettings>,
    value const &jv) noexcept(false) {
  auto const &obj = jv.as_object();
  DataAdapter::DataAdapterSettings settings;
  settings.data_manager_settings =
      deserialize_obj<DataManager::DataManagerSettings>(
          obj, "data_manager_settings");
  settings.image_process_parameters_set =
      deserialize_obj<std::vector<DataAdapter::ImageProcessParameters>>(
          obj, "image_process_parameters_set");
  settings.point_cloud_process_parameters =
      deserialize_obj<DataAdapter::PointCloudProcessParameters>(
          obj, "point_cloud_process_parameters");
  return settings;
}
}  // namespace boost::json

#endif  // DATA_ADAPTER_H_
